package com.cblz.bd.server.utils;

import cn.hutool.core.util.RandomUtil;
import com.cblz.bd.common.core.exception.BdAdminException;
import com.cblz.bd.minio.spring.boot.starter.properties.MinioProperties;
import com.cblz.bd.minio.spring.boot.starter.utils.MinioUtil;
import com.cblz.bd.server.core.event.UploadImageEventListen;
import io.minio.MinioClient;
import io.vavr.Tuple;
import io.vavr.control.Option;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.PostConstruct;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.*;

/**
 * {@code @Name:} MinioFilesUtil
 * {@code @Description: } MinIO文件操作工具类
 * {@code @Author:} by yanjieping
 * {@code @CreateDate:} 2022/11/22 - 周二
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class MinioFilesUtil {

    private final MinioProperties minioProperties;
    private final MinioClient minioClient;
    private final ApplicationEventPublisher publisher;

    @PostConstruct
    public void init() {
        MinioUtil.setMinioClient(minioClient);
    }

    /**
     * 使用MultipartFile进行文件上传
     *
     * @param file      上传文件
     * @param targetDir 存储目标文件夹
     * @return 文件存储外链
     */
    public String uploadFile(MultipartFile file, String targetDir) throws Exception {
        // 判断文件file和targetDir是否为空
        if (!file.isEmpty() && StringUtils.isNotBlank(targetDir)) {
            if (null == file.getOriginalFilename() || StringUtils.isBlank(file.getOriginalFilename()))
                throw new BdAdminException("文件名不能为空字符串");
            // 解析到文件后缀，判断是否合法
            int index = file.getOriginalFilename().lastIndexOf(".");
            String suffix = null;
            if (index == -1 || (suffix = file.getOriginalFilename().substring(index + 1)).isEmpty())
                throw new BdAdminException("文件后缀不能为空");
            // 上传的文件大小设置
            if (file.getSize() > 5000)
                throw new BdAdminException("上传的文件不能超过5MB");
            // 允许上传的文件后缀列表
            Set<String> allowSuffix = new HashSet<>(Arrays.asList("jpg", "jpeg", "png", "gif", "doc"));
            if (!allowSuffix.contains(suffix.toLowerCase()))
                throw new BdAdminException("非法的文件，不允许的文件类型：" + suffix);

            String fileContentType = file.getContentType();
            // 生成对象名，格式：当前时间戳毫秒数+@+上传文件名
            String objectName = new StringJoiner(".").add(String.valueOf(LocalDateTime.now().toInstant(ZoneOffset.of("+8")).toEpochMilli())).add(RandomUtil.randomString(6)).add(Objects.requireNonNull(fileContentType).split("/")[1]).toString();
            log.info("上传文件--文件夹:{},文件名:{}", targetDir, objectName);
            // 上传前校验
            uploadBeforeValidation(targetDir, objectName);
            // 上传文件
            MinioUtil.uploadFile(minioProperties.getBucket(), file, targetDir + "/" + objectName);
            // 获取文件外链，判断外链是否为空，空表示没有上传成功
            // 返回外链
            String url = Option.of(MinioUtil.getPreSignedObjectUrl(minioProperties.getBucket(), targetDir + "/" + objectName)).getOrElse(() -> {
                log.warn("获取文件外链失败，请查看MinIO客户端是否正常");
                throw new BdAdminException("获取文件外链失败，请确认桶策略是否开启可访问");
            });

            publisher.publishEvent(new UploadImageEventListen(Tuple.of(file, objectName, url)));
            return url;
        }
        return null;
    }

    /**
     * 上传前校验相关桶和文件是否存在
     *
     * @param targetDir  文件夹
     * @param objectName 文件名
     * @throws Exception
     */
    public void uploadBeforeValidation(String targetDir, String objectName) throws Exception {
        // 判断桶是否存在
        if (!MinioUtil.bucketExists(minioProperties.getBucket())) {
            log.error("Minio桶「{}」不存在", minioProperties.getBucket());
            throw new BdAdminException("Minio桶不存在");
        }
        // 判断文件是否存在
        if (MinioUtil.isObjectExist(minioProperties.getBucket(), objectName)) {
            log.error("文件名已存在「{}」", objectName);
            throw new BdAdminException("文件已存在");
        }
    }
}
